package ru.vsu.amm.scenes.bulk;

import org.jzy3d.chart.Chart;
import org.jzy3d.chart.controllers.keyboard.camera.CameraKeyController;
import org.jzy3d.colors.Color;
import org.jzy3d.maths.BoundingBox3d;
import org.jzy3d.maths.Coord3d;
import org.jzy3d.plot3d.builder.Builder;
import org.jzy3d.plot3d.primitives.*;
import org.jzy3d.plot3d.rendering.canvas.Quality;
import org.jzy3d.plot3d.rendering.scene.Graph;
import ru.vsu.amm.scenes.AbstractDemo;

import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * User: Saksonov Andrey <saksonov@amm.vsu.ru>
 * Date: 19.12.12
 * Time: 13:55
 */
public class HedgehodDemo extends AbstractDemo {

    private static final float EPS = 0.000001f;

    private int size;
    private Coord3d[] coords;

    private int xmesh;
    private int ymesh;
    private int zmesh;
    private float zstep;
    private float ystep;
    private float xstep;

    private boolean mode1 = false;
    private boolean mode2 = false;
    private boolean mode3 = false;
    private boolean mode4 = false;
    private boolean mode5 = false;
    private boolean mode6 = false;

    private double[][] data;

    private int layer = 0;
    private int layers = 9;

    private float xrange;
    private float yrange;
    private float zrange;

    private HedgehodDemo() {
        // Constructor
    }

    public HedgehodDemo(int xmesh, int ymesh, int zmesh, float xstep, float ystep, float zstep) {
    	this();
        build(xmesh, ymesh, zmesh, xstep, ystep, zstep);
    }

    private static String getInstructions() {
        StringBuilder sb = new StringBuilder();
        sb.append("Выбор элементов        : При помощи клавиш 'Z', 'X', 'C', 'V', 'B', 'N'\n");
        sb.append("Выбор слоя             : При помощи клавиш '1', '2', '3' .. '9', '0'");
        return sb.toString();
    }

    private void setLayer(int layer) throws Exception {
        if (layer >= 0 && layer <= layers) {
            this.layer = layer;
            redraw();
        }
    }

    public double[][] getData() {
        return data;
    }

    public void setData(double[][] data) {
        this.data = data;
    }

    @Override
    public void init() throws Exception {
        chart = new Chart(Quality.Advanced, getCanvasType());
        chart.addController(new CameraKeyController());
        KeyListener modeListener = new KeyListener() {
            @Override
            public void keyTyped(KeyEvent e) {
                try {
                    switch (e.getKeyChar()) {
                        case 'z':
                            switchMode(1);
                            break;
                        case 'x':
                            switchMode(2);
                            break;
                        case 'c':
                            switchMode(3);
                            break;
                        case 'v':
                            switchMode(4);
                            break;
                        case 'b':
                            switchMode(5);
                            break;
                        case 'n':
                            switchMode(6);
                            break;
                        default:
                            break;
                    }
                } catch (Exception ex) {
                    ex.printStackTrace();
                    System.exit(0);
                }
            }

            @Override
            public void keyPressed(KeyEvent e) {
            }

            @Override
            public void keyReleased(KeyEvent e) {
            }
        };
        chart.getCanvas().addKeyListener(modeListener);
        KeyListener layerListener = new
                KeyListener() {
                    @Override
                    public void keyTyped(KeyEvent e) {
                        try {
                            switch (e.getKeyChar()) {
                                case '1':
                                    setLayer(1);
                                    break;
                                case '2':
                                    setLayer(2);
                                    break;
                                case '3':
                                    setLayer(3);
                                    break;
                                case '4':
                                    setLayer(4);
                                    break;
                                case '5':
                                    setLayer(5);
                                    break;
                                case '6':
                                    setLayer(5);
                                    break;
                                case '7':
                                    setLayer(5);
                                    break;
                                case '8':
                                    setLayer(5);
                                    break;
                                case '9':
                                    setLayer(5);
                                    break;
                                case '0':
                                    setLayer(0);
                                    break;
                                default:
                                    break;
                            }
                        } catch (Exception ex) {
                            ex.printStackTrace();
                            System.exit(0);
                        }
                    }

                    @Override
                    public void keyPressed(KeyEvent e) {
                    }

                    @Override
                    public void keyReleased(KeyEvent e) {
                    }
                };
        chart.getCanvas().addKeyListener(layerListener);
        System.out.println(getInstructions());
        redraw();
    }

    public void redraw() {
        draw(data);
    }

    private void switchMode(int mode) throws Exception {
        if (mode == 1) {
            mode1 = !mode1;
        } else if (mode == 2) {
            mode2 = !mode2;
        } else if (mode == 3) {
            mode3 = !mode3;
        } else if (mode == 4) {
            mode4 = !mode4;
        } else if (mode == 5) {
            mode5 = !mode5;
        } else if (mode == 6) {
            mode6 = !mode6;
        }
        redraw();
    }

    private void drawLine(Coord3d begin, Coord3d end, Color beginColor, Color endColor) {
        LineStrip line = new LineStrip();
        line.add(new Point(begin, beginColor));
        line.add(new Point(end, endColor));
        chart.getScene().getGraph().add(line, false);
    }

    private void drawLine(Coord3d begin, Coord3d end) {
        drawLine(begin, end, Color.GREEN, Color.BLUE);
    }

    private void drawPoint(Coord3d crd, Color pointColor) {
        Point point = new Point(crd, pointColor, 3);
        chart.getScene().getGraph().add(point, false);
    }

    private void drawPoint(Coord3d crd) {
        drawPoint(crd, Color.RED);
    }

    private void drawMesh() {
        for (float z = 0; z <= zrange; z = z + zstep) {
            if (layer != 0 && distance((layer - 1) * zstep, z) > EPS) {
                continue;
            }
            Shape surface = Builder.buildDelaunay(filter(coords, coords, z));
            surface.setFaceDisplayed(false);
            surface.setWireframeWidth(1);
            surface.setWireframeDisplayed(true);
            surface.setWireframeColor(Color.RED);
            chart.getScene().getGraph().add(surface, false);
        }
    }

    private void drawMesh(double[][] vectors) {
        if (vectors.length != size) {
            throw new IllegalArgumentException("Неверная размерность массива векторов перемещений!");
        }
        Coord3d newcrds[] = new Coord3d[size];
        for (int i = 0; i < size; i++) {
            newcrds[i] = new Coord3d(coords[i].x + vectors[i][0], coords[i].y + vectors[i][1], coords[i].z + vectors[i][2]);
        }
        for (float z = 0; z <= zrange; z = z + zstep) {
            if (layer != 0 && distance((layer - 1) * zstep, z) > EPS) {
                continue;
            }
            Shape surface = Builder.buildDelaunay(filter(coords, newcrds, z));
            surface.setFaceDisplayed(false);
            surface.setWireframeWidth(1);
            surface.setWireframeDisplayed(true);
            surface.setWireframeColor(Color.MAGENTA);
            chart.getScene().getGraph().add(surface, false);
        }
    }

    private List<Coord3d> filter(Coord3d[] coords1, Coord3d[] coords2, float zfilter) {
        List<Coord3d> fltred = new ArrayList<Coord3d>();
        for (int i = 0; i < size; i++) {
            if (skip(coords1[i], zfilter)) continue;
            fltred.add(coords2[i]);
        }
        return fltred;
    }

    private boolean skip(Coord3d coord3d, float zfilter) {
        if (distance(coord3d.z, zfilter) > EPS) {
            return true;
        }
        return false;
    }

    private void build(int xmesh, int ymesh, int zmesh, float xstep, float ystep, float zstep) {
        this.zstep = zstep;
        this.ystep = ystep;
        this.xstep = xstep;

        this.xmesh = xmesh;
        this.ymesh = ymesh;
        this.zmesh = zmesh;

        this.layers = zmesh + 1;
        this.size = (xmesh + 1) * (ymesh + 1) * (zmesh + 1);
        this.coords = new Coord3d[size];

        this.xrange = xmesh * xstep;
        this.yrange = ymesh * ystep;
        this.zrange = zmesh * zstep;

        int cur = 0;
        for (float x = 0; x < xrange + xstep; x = x + xstep) {
            for (float y = 0; y < yrange + ystep; y = y + ystep) {
                for (float z = 0; z < zrange + zstep; z = z + zstep) {
                    coords[cur] = new Coord3d(x, y, z);
                    cur++;
                }
            }
        }
        System.out.println("Количество элементов в сетке: " + cur);
    }

    private void drawFigure() {
        Parallelepiped pd = new Parallelepiped(new BoundingBox3d(Arrays.asList(coords)));
        pd.setFaceDisplayed(false);
        pd.setWireframeColor(Color.RED);
        pd.setWireframeDisplayed(true);
        pd.setWireframeWidth(1);
        chart.getScene().getGraph().add(pd);
    }

    private void draw(double[][] vectors) {
        if (vectors.length != size) {
            throw new IllegalArgumentException("Неверная размерность входных данных! Размерность параметра: " +
                    vectors.length + ", ожидалось: " + size);
        }
        //rebuild graph
        chart.getScene().setGraph(new Graph(chart.getScene()));
        drawFigure();
        if (mode1) {
            drawPoints();
        }
        if (mode2) {
            drawLines(vectors);
        }
        if (mode3) {
            drawPoints(vectors);
        }
        if (mode4) {
            drawVectors(vectors);
        }
        if (mode5) {
            drawMesh();
        }
        if (mode6) {
            drawMesh(vectors);
        }
    }

    private void drawLines(double[][] vectors) {
        for (int i = 0; i < size; i++) {
            if (skip(i)) continue;
            drawLine(coords[i], new Coord3d(coords[i].x + vectors[i][0], coords[i].y, coords[i].z));
            drawLine(coords[i], new Coord3d(coords[i].x, coords[i].y + vectors[i][1], coords[i].z));
            drawLine(coords[i], new Coord3d(coords[i].x, coords[i].y, coords[i].z + vectors[i][2]));
        }
    }

    private void drawVectors(double[][] vectors) {
        for (int i = 0; i < size; i++) {
            if (skip(i)) continue;
            drawLine(coords[i],
                    new Coord3d(coords[i].x + vectors[i][0],
                            coords[i].y + vectors[i][1], coords[i].z + vectors[i][2]));
        }
    }

    private void drawPoints(double[][] vectors) {
        for (int i = 0; i < size; i++) {
            if (skip(i)) continue;
            drawPoint(new Coord3d(coords[i].x + vectors[i][0], coords[i].y + vectors[i][1], coords[i].z + vectors[i][2]), Color.MAGENTA);
        }
    }

    private void drawPoints() {
        for (int i = 0; i < size; i++) {
            if (skip(i)) continue;
            drawPoint(coords[i]);
        }
    }

    private boolean skip(int i) {
        if (layer != 0 && distance((layer - 1) * zstep, coords[i].z) > EPS) {
            return true;
        }
        return false;
    }

    private float distance(float a, float b) {
        return Math.abs(a - b);
    }
}
